using System;
using System.Collections.Generic;
using System.Text;
using NBusiness.CodeDom.Compiler;
using System.Text.RegularExpressions;
using NBusiness.ESharp.Compiler;

namespace NBusiness.CodeDom
{
    [Serializable]
    public abstract class EntityCompileUnit : IAttachable
    {
        public EntityCompileUnit()
        {
            InitializeAssignmentLogic();
        }

        protected abstract void InitializeAssignmentLogic();

        protected internal virtual void Build(Stack<EntityElement> elements, EntityElementType lastSignificant)
        {
            if (elements == null)
                throw new ArgumentNullException("elements", "Invalid element");

            PostAssignmentAction action = PostAssignmentAction.Continue;
            while(elements.Count > 0 && action == PostAssignmentAction.Continue)
            {
                if (elements.Peek().Type == EntityElementType.Unknown)
                {
                    if (ESharpParser.Expressions.LineComments.IsMatch(elements.Peek().Value))
                    {
                        elements.Peek().Type = EntityElementType.Comment;
                    }
                    else if (ESharpParser.Expressions.WhiteSpace.IsMatch(elements.Peek().Value))
                    {
                        elements.Peek().Type = EntityElementType.WhiteSpace;
                    }
                    else
                    {
                        foreach (AssignmentLogic al in _assignments)
                        {
                            if (al.Assign(elements, lastSignificant))
                            {
                                action = al.PostAssignmentAction;
                                break;
                            }
                        }

                        if (elements.Count > 0) lastSignificant = elements.Peek().Type;
                    }
                }

                //index could progress in the assignment handler
                if (elements.Count > 0 && 
                    action == PostAssignmentAction.Continue)
                {
                    EntityElement e = elements.Pop();
                    Elements.Add(e);

                    if (e.Type == EntityElementType.Unknown)
                    {
                        OnBuildError(new ESharpBuildError(ESharpErrorCode.ES0001, e));
                        break; //Stop parsing Unit if we found an invalid element.
                    }
                }
            }
        }
        
        protected internal abstract void Attach();
        void IAttachable.Attach()
        {
            this.Attach();
        }

        protected virtual EntityElement GetElementFromChild(string path, int line, int col)
        {
            return null;
        }

        public EntityElement GetElementByLocation(string path, int line, int col)
        {
            EntityElement e = null;
            EntityElementType lastSignificant = EntityElementType.Unknown;
            foreach(EntityElement element in Elements)
            {
                int width = element.EndOffset - element.BeginOffset;
                if (element.Path == path && 
                    line == element.Line &&
                    col >= element.Column && col <= element.Column + width)
                {
                    if (element.Type == EntityElementType.Unknown ||
                        element.Type == EntityElementType.WhiteSpace)
                    {
                        //Find out what to assign the theoretical next element based on the previous element.
                        AssignmentLogic al = _assignments.Find(new Predicate<AssignmentLogic>(delegate(AssignmentLogic logic)
                        {
                            return logic.PreviousTypes.Contains(lastSignificant);
                        }));

                        if(al!=null) element.Type = al.AssignmentType;
                    }

                    e = element;
                    break;
                }
                else if (element.Type != EntityElementType.Unknown &&
                    element.Type != EntityElementType.WhiteSpace)
                {
                    lastSignificant = element.Type;
                }
            }
            if (e == null)
                e = GetElementFromChild(path, line, col);

            return e;
        }


        public EntityCompileUnit GetUnitByElement(EntityElement element)
        {
            EntityCompileUnit unit = null;

            if (Elements.Contains(element)) unit = this;

            else unit = GetUnitFromChild(element);

            return unit;
        }

        protected virtual EntityCompileUnit GetUnitFromChild(EntityElement element)
        {
            return null;
        }

        private List<AssignmentLogic> _assignments = new List<AssignmentLogic>();
        protected IList<AssignmentLogic> Assignments
        {
            get { return _assignments; }
        }

        private List<EntityElement> _elements = new List<EntityElement>();
        public List<EntityElement> Elements
        {
            get { return _elements; }
        }

        public event EventHandler<EntityBuildErrorEventArgs> BuildError;
        protected virtual void OnBuildError(EntityBuildError error)
        {
            if (BuildError != null) BuildError(this, new EntityBuildErrorEventArgs(error));
        }
    }

    public delegate void AssignmentLogicHandler(object sender, AssignmentLogicEventArgs e);
    public class AssignmentLogicEventArgs : EventArgs
    {
        private Stack<EntityElement> _elements;
        public Stack<EntityElement> Elements
        {
            get { return _elements; }
        }

        private EntityElementType _previous;
        public EntityElementType Previous
        {
            get { return _previous; }
        }

        public AssignmentLogicEventArgs(Stack<EntityElement> elements, EntityElementType previous)
        {
            _elements = elements;
            _previous = previous;
        }
    }

    public enum PostAssignmentAction
    {
        Continue,
        Stop
    }

    public sealed class AssignmentLogic
    {
        private Regex _mustMatch;
        private EntityElementType _assignmentType;

        public EntityElementType AssignmentType
        {
            get { return _assignmentType; }
        }

        private List<EntityElementType> _previousTypes = new List<EntityElementType>();

        public List<EntityElementType> PreviousTypes
        {
            get { return _previousTypes; }
        }
        private AssignmentLogicHandler _handler;
        private PostAssignmentAction _postAssignmentAction;
        public PostAssignmentAction PostAssignmentAction
        {
            get { return _postAssignmentAction; }
        }

        public AssignmentLogic(
            Regex mustMatch, EntityElementType assignmentType, params EntityElementType[] previousTypes)
            : this(mustMatch, assignmentType, null, PostAssignmentAction.Continue, previousTypes)
        { }

        public AssignmentLogic(
            Regex mustMatch, EntityElementType assignmentType, PostAssignmentAction postAssignmentAction, params EntityElementType[] previousTypes)
            : this(mustMatch, assignmentType, null, postAssignmentAction, previousTypes)
        { }

        public AssignmentLogic(
            Regex mustMatch, EntityElementType assignmentType, AssignmentLogicHandler handler, params EntityElementType[] previousTypes)
            : this(mustMatch, assignmentType, handler, PostAssignmentAction.Continue, previousTypes)
        { }

        public AssignmentLogic(
            Regex mustMatch, 
            EntityElementType assignmentType, 
            AssignmentLogicHandler handler,
            PostAssignmentAction postAssignmentAction,
            params EntityElementType[] previousTypes )
        {
            _mustMatch = mustMatch;
            _previousTypes.AddRange(previousTypes);
            _assignmentType = assignmentType;
            _handler = handler;
            _postAssignmentAction = postAssignmentAction;
        }

        internal bool Assign(Stack<EntityElement> elements, EntityElementType previous)
        {
            bool assign = false;

            if (_mustMatch.IsMatch(elements.Peek().Value))
            {
                foreach(EntityElementType type in _previousTypes)
                {
                    if(type == previous || type == EntityElementType.Any)
                    {
                        assign = true;
                        elements.Peek().Type = _assignmentType;
                        if (_handler != null) 
                            _handler(this, new AssignmentLogicEventArgs(elements, previous));
                        break;
                    }
                }
            }
            return assign;
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder("assign " + _assignmentType.ToString() + " after ");
            for (int x = 0; x < _previousTypes.Count; x++)
            {
                if (x > 0) sb.Append(" | ");
                sb.Append(_previousTypes[x].ToString());
            }

            return sb.ToString();
        }
    }
}
